﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Noris.Schedule.Support;

namespace Noris.Schedule.Planning.ProcessData
{
    #region class ParallelPassOptimizerCls : Třída, která zajišťuje optimalizaci v oblasti paralelních průchodů jednoho kapacitního úkolu
    /// <summary>
    /// Třída, která zajišťuje optimalizaci v oblasti paralelních průchodů jednoho kapacitního úkolu
    /// </summary>
    internal class ParallelPassOptimizerCls : IDisposable
    {
        #region Konstrukce, spuštění, property
        internal ParallelPassOptimizerCls(CapacityRegisterCls capacityRegister, CapacityBatchControlCls capacityBatch)
        {
            this.CapacityRegisterData = capacityRegister;
            this.CapacityBatchControl = capacityBatch;
            this.OptionList = new List<ParallelPassOptimizedOptionCls>();
        }
        void IDisposable.Dispose()
        {
            this.CapacityRegisterData = null;
            this.CapacityBatchControl = null;
            this.OptionList = null;
        }
        internal void Optimize(CapacityPlanWorkItemCls taskItem, PlanningScheduleArgs args, Action<WorkUnitCls> workUnitRegistrator)
        {
            try
            {
                if (taskItem == null)
                    Throw.AplError("Došlo k pokusu o vytvoření optimizeru ParallelPassOptimizerCls bez předání kapacitního úkolu, taskItem je null.");

                this.TaskItem = taskItem;
                this.PlanningArgs = args;
                this.WorkUnitRegistrator = workUnitRegistrator;

                this.OptionList = new List<ParallelPassOptimizedOptionCls>();

                if (!this.TaskItem.WorkPassOptimizationEnabled)
                    Throw.AplError("Task %0 nemá povolenou optimalizaci paralelních průchodů, přesto proběhl pokus o jejich optimalizaci.");

                this.OptimizeRun();
            }
            finally
            {
                this.TaskItem = null;
                this.PlanningArgs = null;
                this.WorkUnitRegistrator = null;
            }
        }
        /// <summary>
        /// Registrační systém kapacit
        /// </summary>
        private CapacityRegisterCls CapacityRegisterData;
        /// <summary>
        /// Řídící subsystém výrobních dávek
        /// </summary>
        private CapacityBatchControlCls CapacityBatchControl;
        /// <summary>
        /// Kapacitní úkol
        /// </summary>
        internal CapacityPlanWorkItemCls TaskItem { get; private set; }
        /// <summary>
        /// Parametry pro plánování.
        /// Pozor, mohou být NULL.
        /// </summary>
        private PlanningScheduleArgs PlanningArgs;
        /// <summary>
        /// Pointer na metodu, která chce dostávat informace o všech vytvořených WorkUnitCls (vkládá si je někam do své paměti).
        /// Protože nemáme k dispozici revezní metodu (Unregistrator), tak do této metody musíme posílat položky WorkUnitCls až tehdy, 
        /// až si budeme jisti, že jsou definitivní (a ne v průběhu optimalizací).
        /// </summary>
        private Action<WorkUnitCls> WorkUnitRegistrator;
        ///<summary> { get; } 
        ///Množství chybějící, toto se bere do plánu. Fyzicky vyráběný počet na tomto tasku (načteno z this.TaskItem.QtyRequired).
        ///</summary>
        private decimal QtyRequired { get { return this.TaskItem.QtyRequired; } }
        /// <summary>
        /// Směr času pro aktuální registraci kapacit aktuální operace
        /// Akceptuje specifika první operace.
        /// </summary>
        private TimeRange.TimeDirection CurrentTaskTimeDir { get { return (this.PlanningArgs != null ? this.PlanningArgs.CurrentTaskTimeDir : this.TaskItem.TimeDir); } }
        /// <summary>
        /// Výchozí čas registrace kapacit pro aktuální task.
        /// Akceptuje specifika první operace.
        /// </summary>
        private DateTime CurrentTaskFlowTime { get { return (this.PlanningArgs != null ? this.PlanningArgs.CurrentTaskFlowTime : this.TaskItem.StartTime); } }
        /// <summary>
        /// Soupis pokusů o optimalizaci
        /// </summary>
        internal List<ParallelPassOptimizedOptionCls> OptionList { get; private set; }
        /// <summary>
        /// Poslední pokus
        /// </summary>
        internal ParallelPassOptimizedOptionCls CurrentOption { get; private set; }
        #endregion
        #region Optimalizace, rozhodovací a výkonný blok
        private void OptimizeRun()
        {
            // Vytvořím první pokus o optimalizaci: vytvořím výchozí paralelní průchody, dané počtem průchodů, počtem kusů atd, a provedu registraci kapacit:
            this.TaskItem.PrepareWorkPasses(true);
            this.RegisterCapacityForTask();
            // Po registraci kapacit proběhla prvotní analýza průchodů, a byl určen stav reálných paralelních průchodů (=časově vedle sebe běžící výrobní linky).

            // Pokud je reálný paralelismus menší než požadovaný, pak bychom měli změnit počet paralelních průchodů v tasku na ten reálný, tím dojde ke snížení potřebných režijních časů TBC a TEC:
            this.OptimizePassCount();
            // Nyní mám v this.CurrentOption variantu, která má optimální počet paralelních průchodů.

            // Pokud je doporučeno změnit množství na jednotlivých průchodech, a dosáhnout tak kratší výrobní doby, provedeme to nyní:
            this.OptimizePassQty();

            // Nyní vyhodnotím nejlepší pokus, a pokud to není ten aktuální (poslední), pak pro tento nejlepší znovu zaregistruji kapacity:
            this.SelectBestOption();
        }
        /// <summary>
        /// Pokud je toho zapotřebí, pak provede optimalizaci POČTU paralelních průchodů, podle doporučeného počtu, 
        /// který vychází z reálně dostupného paralelismu v daných podmínkách systému kapacit
        /// </summary>
        private void OptimizePassCount()
        {
            for (int a = 0; a < 3; a++)
            {   // Nejvýše 3 postupné pokusy:
                if (!this.CurrentOption.IsRecommendChangeWorkPassCount)
                    break;

                // Odregistruji aktuální kapacity, vytvořím počet průchodů daný posledním existujícím návrhem, 
                // a zaregistruji kapacity pro nový (menší) počet průchodů:
                this.UnRegisterCapacityForTask();
                this.TaskItem.PrepareWorkPasses(this.CurrentOption.RealParallelPassCount, true);
                this.RegisterCapacityForTask();
            }
        }
        /// <summary>
        /// Pokud je doporučeno změnit množství na jednotlivých průchodech, a dosáhnout tak kratší výrobní doby, provedeme to zde.
        /// Zde už se nebude provádět změna POČTU průchodů, počet zůstane. Ale pokusíme se určit množství do jednotlivých průchodů tak, aby jejich společný čas byl optimálně využit.
        /// </summary>
        private void OptimizePassQty()
        {
            for (int a = 0; a < 3; a++)
            {
                if (this.CurrentOption.IsRecommendChangeWorkPassCount)
                    break;

                if (!this.CurrentOption.CanOptimizeQty)
                    break;

                // Výpočet určí doporučené množství po zohlednění reálných možností kapacitního systému.
                this.CurrentOption.CalculateRecommendedQty();
                
                // Může se ale stát, že nyní doporučené množství (seznam průchodů a množství) už byl někde jinde aplikován - pak nemá význam jej aplikovat znovu.
                List<PlanningQtyCls> recommendedList = this.CurrentOption.OptimizedQtyList;
                if (this.RecommendedQtyExists(recommendedList))
                    break;

                // Odregistruji aktuální kapacity, vytvořím paralelní průchody dané posledním existujícím návrhem, 
                // a zaregistruji kapacity pro nově (s optimálnějším množstvím) paralelní průchody:
                this.UnRegisterCapacityForTask();
                this.TaskItem.PrepareWorkPasses(recommendedList, true);
                this.RegisterCapacityForTask();
            }
        }
        /// <summary>
        /// Z dosavadních pokusů vybere ten nejlepší
        /// </summary>
        private void SelectBestOption()
        {
            List<ParallelPassOptimizedOptionCls> optionList = new List<ParallelPassOptimizedOptionCls>(this.OptionList);
            if (optionList.Count > 1)
                optionList.Sort(ParallelPassOptimizedOptionCls.CompareByTaskWorkSeconds);       // Setřídím podle času práce, nejkratší čas tasku = nejlepší kombinace
            this.CurrentOption = optionList.FirstOrDefault();
            if (this.CurrentOption == null)
            {
                Throw.SysError(MessageInfo.Get("Došlo k chybě při optimalizaci paralelních průchodů: po dokončení optimalizace nebyl nalezen ani jeden pokus."));
            }

            // Pokud BestOption nemá registrované kapacity, znamená to že nejde o poslední pokus ale o některý dřívější.
            // Pak tedy založím nový pokus, a do něj vložím soupis paralelních průchodů pro množství z tohoto nejlepšího pokusu:
            if (!this.CurrentOption.HasRegisteredCapacity)
            {
                List<PlanningQtyCls> bestQtyList = this.CurrentOption.CurrentPassQtyList;
                // Odregistruji aktuální kapacity, vytvořím paralelní průchody dané nejlepším návrhem, 
                // a zaregistruji kapacity pro tyto paralelní průchody:
                this.UnRegisterCapacityForTask();
                this.TaskItem.PrepareWorkPasses(bestQtyList, true);
                this.RegisterCapacityForTask();
            }

            // V tasku jsou zachyceny kapacity (časy, KPJ) z posledního pokusu, ale jejich WorkUnit jsou v LastOption. Měli bychom je předat do standardní metody registrátoru:
            if (this.CurrentOption.HasRegisteredCapacity)
                this.CurrentOption.SendWorkUnitsTo(this.WorkUnitRegistrator);

            // Transportní dávky:
            this.TaskItem.CapacityBatchRecalc();
            this.CapacityBatchControl.CurrentBatchTask = this.TaskItem.CapacityBatchTask.GetClone();
        }
        /// <summary>
        /// Metoda zjistí, zda soupis množství do paralelních průchodů (parametr recommendedList) již v některém z existujících pokusů byl aplikován jako reálný seznam množství (option.CurrentPassQtyList).
        /// Pokud takový najde, vrátí true.
        /// </summary>
        /// <param name="recommendedList"></param>
        /// <returns></returns>
        private bool RecommendedQtyExists(List<PlanningQtyCls> recommendedList)
        {
            foreach (ParallelPassOptimizedOptionCls option in this.OptionList)
            {
                if (PlanningQtyCls.IsQtyRequiredInListsEqual(option.CurrentPassQtyList, recommendedList))
                    return true;
            }
            return false;
        }
        /// <summary>
        /// Vytvoří novou Option do this.OptionList, bude dostupná v property this.LastOption.
        /// Zaregistruje kapacity pro task, pro všechny jeho WorkPass, bufferuje vytvářené WorkUnit. Nastaví příznak Option.HasRegisteredCapacity = true.
        /// Provede analýzu průchodů z tasku do Option.
        /// </summary>
        private void RegisterCapacityForTask()
        {
            ParallelPassOptimizedOptionCls option = new ParallelPassOptimizedOptionCls(this);
            this.CurrentOption = option;
            this.OptionList.Add(option);

            CapacityRegisterArgs regArgs = CapacityRegisterArgs.Create(this.CapacityBatchControl, option.WorkUnitRegistrator);
            this.CapacityRegisterData.RegisterCapacityForCTask(this.TaskItem, regArgs);
            option.HasRegisteredCapacity = true;

            option.FillPassListFromTask(this.TaskItem);
        }
        /// <summary>
        /// Odregistruje kapacity tasku
        /// </summary>
        private void UnRegisterCapacityForTask()
        {
            ParallelPassOptimizedOptionCls option = this.CurrentOption;
            this.CapacityRegisterData.UnRegisterCapacity(this.TaskItem, option.WorkUnitUnRegistrator);
            option.HasRegisteredCapacity = false;
        }
        #endregion
    }
    #endregion
    #region class ParallelPassOptimizedOptionCls : Informace o jednom pokusu o optimalizaci několika paralelních průchodů.
    /// <summary>
    /// Informace o jednom pokusu o optimalizaci několika paralelních průchodů.
    /// Jednotlivé paralelní průchody jsou popsány uvnitř této třídy v seznamu PassList.
    /// Obsahuje celkový čas ze všech svých průchodů, napomáhá k vyhodnocení optimálnosti.
    /// </summary>
    internal class ParallelPassOptimizedOptionCls
    {
        #region Konstrukce, property
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="parent"></param>
        internal ParallelPassOptimizedOptionCls(ParallelPassOptimizerCls parent)
        {
            this.Parent = parent;
            this.HasRegisteredCapacity = false;
            this.PassList = new List<ParallelPassOptimizedOptionOnePassCls>();
            this.CurrentPassQtyList = new List<PlanningQtyCls>();
            this.OptimizedQtyList = null;
            this._RegisteredWorkUnits = new Dictionary<int, WorkUnitCls>();
        }
        public override string ToString()
        {
            return "Option: PassCount: " + this.PassList.Count.ToString() + "; TaskTime: " + this.TaskWorkSeconds.ToString() + " secs = " + this.TaskWorkTime.StringLong;
        }
        /// <summary>
        /// Vztah na parent = optimizer
        /// </summary>
        internal ParallelPassOptimizerCls Parent { get; private set; }
        /// <summary>
        /// Příznak, zda tento pokus má/nemá registrované kapacity
        /// </summary>
        internal bool HasRegisteredCapacity { get; set; }
        /// <summary>
        /// Data o paralelních průchodech
        /// </summary>
        internal List<ParallelPassOptimizedOptionOnePassCls> PassList { get; private set; }
        /// <summary>
        /// Seznam množství, které vychází z aktuálních paralelních průchodů (tj. reálně zaplánovaná, odpovídají datům PassList)
        /// </summary>
        internal List<PlanningQtyCls> CurrentPassQtyList { get; private set; }
        /// <summary>
        /// Seznam množství, které doporučuje optimalizační algoritmus pro další chod
        /// </summary>
        internal List<PlanningQtyCls> OptimizedQtyList { get; private set; }
        /// <summary>
        /// Časový interval mezi počátkem registrace kapacit (požadovaný datum) a reálně poslední registrovanou kapacitou.
        /// Reaguje na směr registrace kapacit.
        /// </summary>
        internal TimeRange TaskWorkTime { get; private set; }
        /// <summary>
        /// Počet sekund od počátku registrace kapacit (požadovaný datum) a reálně poslední registrovanou kapacitou.
        /// Srovnávací údaj: menší číslo = lepší výsledek.
        /// </summary>
        internal decimal TaskWorkSeconds { get; private set; }
        /// <summary>
        /// Data o reálných paralelních chodech.
        /// Pokud například máme 10 paralelních průchodů v this.PassList, ale všechny jsou za sebou jak housata, pak reálně máme jeden paralelní chod.
        /// Reálný paralelismus je určen v metodě CalculateRealParallelism(), která je volána v procesu FillPassListFromTask().
        /// Do té doby je zde null.
        /// </summary>
        internal List<decimal> RealParallelPass { get; private set; }
        /// <summary>
        /// Buffer objektů WorkUnitCls, které by měly být zaregistrovány.
        /// K registraci slouží metoda this.SendWorkUnitsTo().
        /// </summary>
        private Dictionary<int, WorkUnitCls> _RegisteredWorkUnits;
        /// <summary>
        /// Komparátor dvou not null záznamů podle hodnoty TaskWorkSeconds ASC
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        internal static int CompareByTaskWorkSeconds(ParallelPassOptimizedOptionCls a, ParallelPassOptimizedOptionCls b)
        {
            return a.TaskWorkSeconds.CompareTo(b.TaskWorkSeconds);
        }
        #endregion
        #region Registrace a odregistrace objektů WorkUnit. Naplnění paralelních průchodů po registraci kapacit.
        /// <summary>
        /// Registrátor objektů WorkUnitCls, které vznikají při registraci kapacit v tomto pokusu.
        /// Objekty nejsou posílány do externí metody, ale jsou bufferovány zde.
        /// Pokud se optimizer (nadřízená jednotka) rozhodne, že tato varianta (option) bude ponechána jako reálná pro další kapacitní plánování,
        /// pak musí zdejší zachycené objekty WorkUnitCls odeslat do reálného registrátoru (workUnitRegistrator), a to tak že zavolá zdejší metodu
        /// this.SendWorkUnitsTo(a předá jí pointer na metodu reálného registrátora: workUnitRegistrator).
        /// </summary>
        internal void WorkUnitRegistrator(WorkUnitCls workUnit)
        {
            this._RegisteredWorkUnits.Add(workUnit.WorkID, workUnit);
        }
        /// <summary>
        /// Odregistrátor objektů WorkUnitCls, které se odebírají při odregistrování kapacit v tomto pokusu.
        /// Objekty nejsou odebírány prostřednictvím externí metody, ale jsou odebírány ze zdejšího bufferu.
        /// </summary>
        internal void WorkUnitUnRegistrator(WorkUnitCls workUnit)
        {
            if (this._RegisteredWorkUnits.ContainsKey(workUnit.WorkID))
                this._RegisteredWorkUnits.Remove(workUnit.WorkID);
        }
        /// <summary>
        /// Všechny položky WorkUnitCls, které byly do tohoto objektu vloženy metodou this.WorkUnitRegistrator() (=registrace do bufferu) odešle do reálného registrátoru WorkUnit (parametr).
        /// </summary>
        /// <param name="workUnitRegistrator"></param>
        internal void SendWorkUnitsTo(Action<WorkUnitCls> workUnitRegistrator)
        {
            if (workUnitRegistrator != null)
            {
                foreach (WorkUnitCls workUnit in this._RegisteredWorkUnits.Values)
                    workUnitRegistrator(workUnit);
            }
            this._RegisteredWorkUnits.Clear();
        }
        /// <summary>
        /// Načte do sebe data z paralelních průchodů daného tasku
        /// </summary>
        /// <param name="taskItem"></param>
        internal void FillPassListFromTask(CapacityPlanWorkItemCls taskItem)
        {
            this.PassList.Clear();
            foreach (WorkPassCls workPass in taskItem.WorkPassList)
            {
                this.PassList.Add(new ParallelPassOptimizedOptionOnePassCls(this, workPass));
                this.CurrentPassQtyList.Add(new PlanningQtyCls(workPass.QtyRequired, workPass.QtyFinal));
            }
            this.CalculateTimeOverwiev(taskItem);
            this.CalculateRealParallelism();
        }
        /// <summary>
        /// Vypočítá údaje, které slouží k vyhodnocení úspěšnosti této volby.
        /// Určí časový interval od počátku registrace kapacit až k poslednímu zaregistrovanému času, 
        /// odvodí z něj délku operace v sekundách (=obecně srovnávací etalon úspěšnosti: menší číslo je lepší).
        /// </summary>
        /// <param name="taskItem"></param>
        private void CalculateTimeOverwiev(CapacityPlanWorkItemCls taskItem)
        {
            TimeRange passTimeSum = TimeRange.Empty;
            foreach (ParallelPassOptimizedOptionOnePassCls pass in this.PassList)
                passTimeSum += pass.PassWorkTime;

            DateTime startTime = taskItem.StartTime;
            TimeRange.TimeDirection timeDir = taskItem.GetTimeDirForPhase(OperationPhase.TAC);       // Směr času registrace kapacit práce (TAC)
            switch (timeDir)
            {
                case TimeRange.TimeDirection.ToFuture:
                    // Pokud reálné kapacity začínají až za startTime, pak musím jako začátek tasku chápat startTime:
                    if (passTimeSum.Begin > startTime)
                        passTimeSum.Begin = startTime;
                    break;
                case TimeRange.TimeDirection.ToHistory:
                    // Pokud reálné kapacity končí před startTime, pak musím jako konec tasku chápat startTime:
                    if (passTimeSum.End < startTime)
                        passTimeSum.End = startTime;
                    break;
            }
            this.TaskWorkTime = passTimeSum;
            this.TaskWorkSeconds = (decimal)passTimeSum.Seconds;
        }
        #endregion
        #region Výpočet reálného paralelismu
        /// <summary>
        /// Metoda vypočítá reálný paralelismus v tomto pokusu
        /// </summary>
        internal void CalculateRealParallelism()
        {
            this.RealParallelPass = new List<decimal>();

            // Zkratky pro 0 a pro 1 průchod:
            if (this.PassList.Count == 0) return;
            if (this.PassList.Count == 1)
            {   // Pokud existuje jen jeden průchod, pak výsledek je roven jeho času:
                this.RealParallelPass.Add((decimal)(this.PassList[0].PassWorkTime.Seconds));
                return;
            }

            // Více průchodů si zaslouží analýzu:
            // Prozatím je analýza jednoduchá (začátek a konec paralelního průchodu).
            // Byla by teoreticky možná i analýza detailní = když bychom šli na úroveň jednotlivých časů v každém průchodu, ale ty si zatím z WorkPassCls neopisujeme do ParallelPassOptimizedOptionOnePassCls.
            List<OneTimeChangeCls> changes = new List<OneTimeChangeCls>();    // Pole, obsahuje datum změny a počet změny: začátek průchodu = +1, konec průchodu = -1.
            foreach (ParallelPassOptimizedOptionOnePassCls pass in this.PassList)
            {   // Začátek a konec průchodu uložím jako jednotkové změny:
                changes.Add(new OneTimeChangeCls(pass.PassWorkTime.Begin, 1));
                changes.Add(new OneTimeChangeCls(pass.PassWorkTime.End, -1));
            }
            changes.Sort(OneTimeChangeCls.CompareByDateQty);

            // Nyní máme lineární setříděný soupis změn: kdy došlo ke zvýšení a ke snížení počtu paralelních průchodů.
            // Sestavíme z toho soupis reálně paralelních chodů = kdy se skutečně vyrábělo vedle sebe, a jak dlouho se to dařilo:
            this.RealParallelPassList = new List<OneRealPassCls>();
            int currentPass = 0;
            foreach (OneTimeChangeCls change in changes)
            {
                if (change.QtyChange > 0)
                {   // Začíná práce na dalším průchodu:
                    currentPass++;
                    while (this.RealParallelPassList.Count < currentPass)
                        this.RealParallelPassList.Add(new OneRealPassCls(change.DateChange));

                    this.RealParallelPassList[currentPass - 1].StartPass(change.DateChange);
                }
                else if (change.QtyChange < 0)
                {   // Končí práce na aktuálním průchodu:
                    this.RealParallelPassList[currentPass - 1].EndPass(change.DateChange);
                    currentPass--;
                }
            }

            // Konverze (OneRealPassCls => decimal):
            this.RealParallelPass = this.RealParallelPassList.ConvertAll(p => p.TotalSeconds);
        }
        /// <summary>
        /// Počet paralelních průchodů načtených z tasku (= počet položek pole this.PassList)
        /// </summary>
        internal int TaskWorkPassCount { get { return this.PassList.Count; } }
        /// <summary>
        /// Počet reálných paralelních chodů (=skutečně vedle sebe běžící výrobní linky), určeno časovou analýzou průchodů this.PassList.
        /// </summary>
        internal int RealParallelPassCount { get { return (this.RealParallelPassList == null ? 0 : this.RealParallelPassList.Count); } }
        /// <summary>
        /// Příznak který říká, zda se má změnit POČET paralelních průchodů.
        /// To je tehdy, když do tasku bylo zadáno a registrováno N1 průchodů (=TaskWorkPassCount) (typicky počet vycházející z STPV), 
        /// ale reálně paralelních chodů bylo N2 (=RealParallelPassCount) (počet je určen časovou analýzou průchodů this.PassList).
        /// </summary>
        internal bool IsRecommendChangeWorkPassCount
        {
            get
            {
                if (this.TaskWorkPassCount < 2) return false;             // Když už teď má kapacitní úkol méně než 2 průchody, pak se nedá nic optimalizovat.
                if (this.RealParallelPassList == null) return false;      // Dokud nebudou výsledky analýzy, nedá se s jistotou doporučit optimalizace.
                return (this.RealParallelPassList.Count < this.TaskWorkPassCount);  // Optimalizovat: když reálný počet je menší než požadovaný počet.
            }
        }
        private List<OneRealPassCls> RealParallelPassList;
        #region class OneTimeChangeCls : jedna změna počtu paralelních průchodů na časové ose
        /// <summary>
        /// Třída, popisující datum a čas změny, a množství změny.
        /// </summary>
        private class OneTimeChangeCls
        {
            internal OneTimeChangeCls(DateTime dateChange, int qtyChange)
            {
                this.DateChange = new DateTime(dateChange.Year, dateChange.Month, dateChange.Day, dateChange.Hour, dateChange.Minute, dateChange.Second);      // Odřezávám milisekundy
                this.QtyChange = qtyChange;
            }
            public override string ToString()
            {
                return this.DateChange.ToString("dd.MM.yyyy HH:mm:ss") + " => " + (this.QtyChange > 0 ? "+" : "") + this.QtyChange.ToString();
            }
            internal DateTime DateChange;
            internal int QtyChange;
            /// <summary>
            /// Komparátor podle data změny (ASC), a pro shodné datum pak podle množství (ASC).
            /// Řadí tedy nejprve starší záznamy, a pro záznamy ze stejného okamžiku dává nejprve záporné a pak kladné.
            /// </summary>
            /// <param name="a"></param>
            /// <param name="b"></param>
            /// <returns></returns>
            internal static int CompareByDateQty(OneTimeChangeCls a, OneTimeChangeCls b)
            {   // Pominu možnost, že a nebo b je null!
                int cmp = a.DateChange.CompareTo(b.DateChange);
                if (cmp == 0)
                    cmp = a.QtyChange.CompareTo(b.QtyChange);
                return cmp;
            }
        }
        #endregion
        #region class OneRealPassCls : jeden reálný paralelní průchod (může být složen z více fragmentů)
        /// <summary>
        /// Třída, popisující průběh jednoho reálného paralelního chodu.
        /// Slouží k průběžnému nápočtu, vycházejícího z dat o změnách (List instancí třídy OneTimeChangeCls).
        /// Obsahuje informaci, kolik sekund se reálně vyrábělo (čas se odvozuje z času ParallelPassOptimizedOptionOnePassCls.WorkPassTime, ne pouze TAC), a z kolika fragmentů se skládá.
        /// </summary>
        private class OneRealPassCls
        {
            internal OneRealPassCls(DateTime begin)
            {
                this.CurrentBegin = null;
                this.FirstBegin = begin;
                this.LastEnd = begin;
                this.TotalSeconds = 0m;
                this.TotalFragmentCount = 0;
            }
            public override string ToString()
            {
                return "Begin: " + this.FirstBegin.ToString("dd.MM.yyyy HH:mm:ss") + "; End: " + this.LastEnd.ToString("dd.MM.yyyy HH:mm:ss") +
                    "; Fragments: " + this.TotalFragmentCount.ToString() + "; Seconds: " + this.TotalSeconds.ToString() +
                    "; State: " + (this.CurrentBegin.HasValue ? "working from " + this.CurrentBegin.Value.ToString("dd.MM.yyyy HH:mm:ss") : "waiting") + ".";
            }
            private DateTime? CurrentBegin;
            internal DateTime FirstBegin { get; private set; }
            internal DateTime LastEnd { get; private set; }
            internal decimal TotalSeconds { get; private set; }
            internal int TotalFragmentCount { get; private set; }

            /// <summary>
            /// Zaregistruje datum, kdy začal aktivní úsek (začátek práce ve WorkPass), od daného data.
            /// </summary>
            /// <param name="begin"></param>
            internal void StartPass(DateTime begin)
            {
                if (this.CurrentBegin.HasValue)
                    Throw.SysError(MessageInfo.Get("PassOptimizer: Nelze zahájit úsek práce OneRealPassCls v situaci, kdy je již zahájen a není dokončen. Metoda StartPass(%0) je volána v situaci, kdy CurrentBegin obsahuje hodnotu %1.", begin, this.CurrentBegin));

                this.CurrentBegin = begin;
            }
            /// <summary>
            /// Zaregistruje datum, kdy skončil aktivní úsek (konec práce ve WorkPass), k danému datu.
            /// </summary>
            /// <param name="end"></param>
            internal void EndPass(DateTime end)
            {
                if (!this.CurrentBegin.HasValue)
                    Throw.SysError(MessageInfo.Get("PassOptimizer: Nelze ukončit úsek práce OneRealPassCls v situaci, kdy nebyl zahájen. Metoda EndPass(%0) je volána v situaci, kdy CurrentBegin obsahuje hodnotu NULL.", end));

                this.LastEnd = end;
                this.TotalFragmentCount++;
                this.TotalSeconds += (decimal)(((TimeSpan)(end - this.CurrentBegin.Value)).TotalSeconds);
                this.CurrentBegin = null;
            }
        }
        #endregion
        #endregion
        #region Určení vhodných množství do paralelních průchodů
        /// <summary>
        /// Příznak, že má smysl řešit optimalizaci množství v paralelních průchodech. true = existují dva nebo více průchodů, false = existuje jen jeden průchod.
        /// </summary>
        internal bool CanOptimizeQty { get { return (this.PassList.Count >= 2); } }
        /// <summary>
        /// Přepočítá do jednotlivých paralelních průchodů (do prvků seznamu this.PassList) doporučované množství, tak aby výsledný čas celého tasku byl optimálně využitý.
        /// </summary>
        internal void CalculateRecommendedQty()
        {
            this.ResetRecommendedQty();
            if (this.PassList.Count < 2) return;

            //  Princip optimalizace množství:
            // 1. Všechny paralelní průchody se začaly registrovat ke stejnému datu a směru času, ale každý skončí jindy. 
            //    Přitom běží vedle sebe, ne za sebou (to optimalizuje blok "Výpočet reálného paralelismu").
            // 2. Časy jednotlivých paralelních průchodů končí různě, což je dáno disponibilními kapacitami (konec registrace = dle směru času: ToFuture: End, ToHistory: Begin).
            // 3. Prostor času daný koncem prvního a koncem posledního průchodu je prostor určený pro optimalizace.
            // 4. V tomto prostoru určím časy TAC (normalizované sekundy) pro všechny průchody - jednotlivě se uloží do průchodu, 
            //    a přitom se i sečtou (do tacToOptimize), a rozdělí se na daný počet průchodů.
            // 5. Vypočtený údaj optimální TAC (tacOneToOptimize) se pošle do každého průchodu, kde se porovná s jeho uloženým údajem (z bodu 4), a určí modifikaci množství.
            //    Viz metoda ParallelPassOptimizedOptionOnePassCls.CalculateRecomendedQty().

            // Směr času TAC a okamžik hrany, kde začíná oblast optimalizace:
            TimeRange.TimeDirection tacTimeDir = this.Parent.TaskItem.GetTimeDirForPhase(OperationPhase.TAC);
            DateTime? edge = this.DetectEdgeTime(tacTimeDir);
            if (!edge.HasValue)
                return;

            // Určím jednotlivé sumy časů TAC, které se nacházejí před hranou edge (tj. v oblasti, která se bude optimalizovat), a sumu časů TAC k optimalizaci:
            decimal tacToOptimize = 0m;
            foreach (ParallelPassOptimizedOptionOnePassCls pass in this.PassList)
            {
                pass.DetectTAcToOptimize(edge.Value, tacTimeDir);
                tacToOptimize += pass.TAcSecondsInOptimizeZone;
            }

            // Optimální čas TAC, který by měl každý jeden průchod pstrávit v optimalizované zóně:
            decimal tacOneToOptimize = tacToOptimize / this.PassList.Count;

            // Vypočítám doporučené množství pro každý průchod v závislosti na jeho vlastním čase TAC v optimalizované zóně, přitom nastřádám seznam množství pro zaokrouhlování:
            List<PlanningQtyCls> requestList = new List<PlanningQtyCls>();
            foreach (ParallelPassOptimizedOptionOnePassCls pass in this.PassList)
            {
                pass.CalculateRecommendedQty(tacOneToOptimize);
                if (pass.RecommendedQty != null)
                    requestList.Add(pass.RecommendedQty);
            }

            // Zarovnám množství a vepíšu jej do this jako doporučené pro další chod:
            this.OptimizedQtyList = this.Parent.TaskItem.GetRoundWorkPassQtyList(requestList);
        }
        /// <summary>
        /// Reset hodnot, které mají vztah k doporučené změně množství
        /// </summary>
        private void ResetRecommendedQty()
        {
            foreach (ParallelPassOptimizedOptionOnePassCls pass in this.PassList)
                pass.ResetRecommendedQty();
        }
        /// <summary>
        /// Vrátí datum, které tvoří hranu optimalizované oblasti
        /// </summary>
        /// <param name="timeDir"></param>
        /// <returns></returns>
        private DateTime? DetectEdgeTime(TimeRange.TimeDirection timeDir)
        {
            DateTime edge;
            switch (timeDir)
            {
                case TimeRange.TimeDirection.ToFuture:
                    // Registrujeme ToFuture: hledáme nejčasnější konec (End, který je nejvíce vlevo):
                    edge = this.PassList.Min(p => p.TAcTime.End);
                    return edge;
                case TimeRange.TimeDirection.ToHistory:
                    // Registrujeme ToHistory: hledáme nejpozdější začátek (Begin, který je nejvíce vpravo):
                    edge = this.PassList.Max(p => p.TAcTime.Begin);
                    return edge;
            }
            return null;
        }
        #endregion
    }
    #endregion
    #region class ParallelPassOptimizedOptionOnePassCls : data o jednom paralelním průchodu opsaná z WorkPassCls po registraci jeho kapacit (data zde vydrží i po odregistraci kapacit)
    /// <summary>
    /// Informace o jednom paralelním průchodu v rámci pokusu o optimalizaci.
    /// Zhruba odpovídá datům ve třídě WorkPassCls, kde je evidován paralelní průběh - ale permanentně a se všemi daty, 
    /// kdežto zde je dočasně a je ukládán jen jeho abstrakt, potřebný pro optimalizace.
    /// Data jsou opsaná z WorkPassCls po registraci jeho kapacit (data zde vydrží i po odregistraci kapacit).
    /// </summary>
    internal class ParallelPassOptimizedOptionOnePassCls
    {
        #region Konstrukce, property
        internal ParallelPassOptimizedOptionOnePassCls(ParallelPassOptimizedOptionCls parent, WorkPassCls workPass)
        {
            this.Parent = parent;

            this.WorkTimeTacList = new List<ParallelPassOptimizedOptionOneTimeCls>();

            // Jaký čas budu brát jako čas průchodu?
            // Čas workPass.TimeWork není vhodný, protože obsahuje i MoveTime, 
            //  kterýžto nežere kapacity, a proto se může vytvářet dojem reálného paralelismu.
            // Buď budu brát časy TBC ÷ TAC ÷ TEC (což je jen odhad), 
            // anebo provedu sumarizaci z jednotlivých časů, které mají kladný počet KPJ (což je přesnější).
            // Přesnost vyhrála:
            TimeRange passTime = TimeRange.Empty;
            TimeRange tacTime = TimeRange.Empty;
            foreach (WorkTimeCls workTime in workPass.WorkTimeLst)
            {
                if (workTime.ContainAnyWork)
                    passTime += workTime.WorkTime;
                if (workTime.Phase == OperationPhase.TAC)
                {
                    tacTime += workTime.WorkTime;
                    this.WorkTimeTacList.Add(new ParallelPassOptimizedOptionOneTimeCls(workTime));
                }
            }

            this.PassWorkTime = passTime;
            this.TAcTime = tacTime;
            this.TAcSeconds = this.WorkTimeTacList.Sum(p => p.SecondsNormalized);
            this.RealQty = new PlanningQtyCls(workPass.QtyRequired, workPass.QtyFinal);
            this.RecommendedQty = null;

            ITaskTimeData taskTime = workPass as ITaskTimeData;               // Při pohledu na ITaskTimeData uvidíme data klíčová pro registraci a podklady pro optimalizaci
            this.TBcReq = taskTime.TBcReq;
            this.TEcReq = taskTime.TEcReq;
        }
        public override string ToString()
        {
            return this.PassWorkTime.StringShort + "; Qty: " + this.RealQty.QtyRequired.ToString();
        }
        /// <summary>
        /// Vztah na parent = jeden ucelený pokus o optimalizaci
        /// </summary>
        internal ParallelPassOptimizedOptionCls Parent { get; private set; }
        /// <summary>
        /// Časový interval, ve kterém jsou obsazeny reálné kapacity (libovolná fáze, ale existují vztahy na KPJ).
        /// Slouží pro optimalizaci počtu paralelních průchodů, nikoli množství v průchodech.
        /// </summary>
        internal TimeRange PassWorkTime { get; private set; }
        /// <summary>
        /// Seznam časů TAC tohoto paralelního průchodu (časový interval a normalizovaný pracovní čas v sekundách).
        /// Zde jsou opsány i časy WorkTimeCls, které nemají žádnou jednotku (tedy bez ohledu na hodnotu WorkTimeCls.ContainAnyWork).
        /// </summary>
        internal List<ParallelPassOptimizedOptionOneTimeCls> WorkTimeTacList { get; private set; }
        /// <summary>
        /// Časový interval, ve kterém probíhá vlastní výroba = TAC. Bez ohledu na obsazování kapacitních jednotek.
        /// Slouží pro optimalizaci množství v průchodech, nikoli počtu paralelních průchodů.
        /// </summary>
        internal TimeRange TAcTime { get; private set; }
        /// <summary>
        /// Součet normalizovaných sekund časů TAC (=this.WorkTimeTacList.Sum(SecondsNormalized).
        /// Obsahuje tedy pouze časy TAC, bez ohledu na obsazování kapacitních jednotek.
        /// </summary>
        internal decimal TAcSeconds { get; private set; }
        /// <summary>
        /// Množství fyzicky vyráběné v tomto průchodu, bez optimalizace.
        /// </summary>
        internal PlanningQtyCls RealQty { get; private set; }
        /// <summary>
        /// Množství optimalizované, není nijak zaokrouhlené ani zarovnané, jde o výsledek prostého výpočtu.
        /// Dokud neproběhla optimalizace, je zde null.
        /// </summary>
        internal PlanningQtyCls RecommendedQty { get; private set; }
        /// <summary>
        /// Definovaný čast TBC
        /// </summary>
        internal decimal TBcReq { get; private set; }
        /// <summary>
        /// Definovaný čast TEC
        /// </summary>
        internal decimal TEcReq { get; private set; }
        /// <summary>
        /// Počet normalizovaných sekund tohoto průchodu, které se nachází v oblasti doporučené optimalizace
        /// </summary>
        internal decimal TAcSecondsInOptimizeZone { get; private set; }
        #endregion
        #region Výpočty pro určení hodnot doporučeného (optimalizovaného) množství
        /// <summary>
        /// Resetuje hodnoty, které řeší doporučené množství
        /// </summary>
        internal void ResetRecommendedQty()
        {
            this.TAcSecondsInOptimizeZone = 0m;
            this.RecommendedQty = null;
        }
        /// <summary>
        /// Detekuje součet svých normalizovaných časů TAC, které se nacházejí před hranou edge (v daném směru času).
        /// Součet ukládá do this.TAcNormSecToOptimize.
        /// </summary>
        /// <param name="edge"></param>
        internal void DetectTAcToOptimize(DateTime edge, TimeRange.TimeDirection timeDir)
        {
            // Určíme množství času TAC, které se za tento paralelní průchod nachází v "optimalizované zóně" 
            //  = časový interval, ve kterém už některé paralelní průchody skončily a jiné ještě ne:
            decimal sum = 0m;
            foreach (ParallelPassOptimizedOptionOneTimeCls time in this.WorkTimeTacList)
            {
                TimeRange split = time.Time.SplitUp(edge, timeDir);                 // Přiměřená část časového intervalu před hranou (v daném směru)
                double seconds = split.Seconds;
                if (seconds > 0d)
                    sum += (decimal)seconds * time.StandardRatio;
            }
            this.TAcSecondsInOptimizeZone = sum;
        }
        /// <summary>
        /// Dopočítá odchylku množství od množství aktuálního, na základě odchylky zdejšího času TAC (this.TAcSecondsInOptimizeZone) od optimálního času (parametr tacSecOptimized).
        /// Optimální čas je určen tak, aby se zprůměrovaly časy všech paralelních průchodů v oblasti času, kdy už některé průchody byly ukončeny a jiné ještě ne.
        /// </summary>
        /// <param name="tacSecOptimized"></param>
        internal void CalculateRecommendedQty(decimal tacSecOptimized)
        {
            this.RecommendedQty = new PlanningQtyCls();

            if (this.RealQty.QtyRequired <= 0m) return;

            // V tomto průchodu máme čas v "optimalizované zóně" (určený v metodě this.DetectTAcToOptimize()) = this.TAcSecondsInOptimizeZone,
            // ale z hlediska souhry všech paralelních průchodů by měl být == (tacSecOptimized) (parametr zdejší metody).
            // Nemůžeme ovlivnit čas přímo, ale můžeme korigovat vyráběné množství.
            // Vypočteme tedy množství QtyRequired, které by odpovídalo času optimálnímu:
            decimal tacOne = this.TAcSeconds / this.RealQty.QtyRequired;      // Normalizovaný čas TAC na jednotku množství (=celkem čas / množství)
            decimal tacTotal = this.TAcSeconds - this.TAcSecondsInOptimizeZone + tacSecOptimized;  // Čas doporučený: aktuální celkem - aktuální prostor optimalizované zóny + doporučený čas v optimalizované zóně
            decimal qtyRequired = tacTotal / tacOne;                          // Množství vyrobené za doporučený čas

            if (this.RealQty.IsSame)
                this.RecommendedQty = new PlanningQtyCls(qtyRequired);
            else
                this.RecommendedQty = new PlanningQtyCls(qtyRequired, qtyRequired * this.RealQty.QtyFinal / this.RealQty.QtyRequired);
        }
        #endregion
    }
    #endregion
    #region class ParallelPassOptimizedOptionOneTimeCls : Informace o jednom čase (pochází z WorkTimeCls)
    /// <summary>
    /// Informace o jednom čase (pochází z WorkTimeCls)
    /// </summary>
    internal class ParallelPassOptimizedOptionOneTimeCls
    {
        internal ParallelPassOptimizedOptionOneTimeCls(WorkTimeCls workTime)
        {
            this.Time = workTime.WorkTime;
            this.StandardRatio = workTime.StandardRatio;
        }
        public override string ToString()
        {
            return this.Time.StringShort + "; Ratio: " + this.StandardRatio.ToString();
        }
        /// <summary>
        /// Časový interval (od-do) tohoto času
        /// </summary>
        internal TimeRange Time { get; private set; }
        /// <summary>
        /// Poměr využití kapacit v tomto čase.
        /// Nemůže být 0 a záporný (to by se nic nevyrobilo ani za sto let).
        /// Hodnota 1 = 100% plnění norem.
        /// Hodnota 0.75 znamená 75% plnění norem, tzn. za 1 hodinu fyzického času (WorkTime) se odpracuje 0.75 * 1h = 45 minut z potřebné kapacity úkolu.
        /// </summary>
        internal decimal StandardRatio { get; private set; }
        /// <summary>
        /// Normalizovaný počet sekund, je dán časem a koeficientem
        /// </summary>
        internal decimal SecondsNormalized { get { return (decimal)this.Time.Seconds * this.StandardRatio; } }
    }
    #endregion
}
